package com.syh.core.ddd.infrastructure.repository;

import cn.redscarf.commons.util.bean.ExtBeanUtils;
import cn.redscarf.commons.util.bean.Holder;
import cn.redscarf.commons.util.reflection.ExtReflectionUtils;
import cn.redscarf.commons.wrapper.Restrictions;
import cn.redscarf.commons.wrapper.Wrapper;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.syh.core.ddd.domain.Aggregate;
import com.syh.core.expction.UnexpectedlyException;
import com.syh.jn.scm.infrastructure.repository.utils.MybatisTemplate;
import com.syh.jn.scm.infrastructure.wrapper.BaseResolve;
import com.syh.jn.scm.infrastructure.wrapper.ResolveToHQL;
import com.syh.jn.scm.infrastructure.wrapper.ResolveToSQL;
import org.apache.ibatis.jdbc.SQL;
import org.hibernate.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate5.HibernateTemplate;

import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * hibernate数据操作抽象类,封装了基本crud方法,针对单个领域对象对应单个数据模型,并不适应单个领域对象对应多个数据模型
 *
 * @param <Entity> 领域对象
 * @param <Model>  数据模型
 * @param <ID>     id
 */
@Deprecated
public abstract class AbstractAggregateRepository<Entity extends Aggregate<ID>, Model, ID extends Serializable> implements AggregateRepository<Entity, ID> {
  @Autowired
  public HibernateTemplate hibernateTemplate;

  private Logger logger = LoggerFactory.getLogger(this.getClass());

  // 实体类类型(由构造方法自动赋值)
  private Class<Entity> aggregateClass = null;
  //数据模型类型
  private Class<Model> modelClass = null;


  /**
   * 构造方法，根据实例类自动获取实体类类型
   */
  public AbstractAggregateRepository() {
    Class c = this.getClass();
    Type t = c.getGenericSuperclass();
    if (t instanceof ParameterizedType) {
      Type[] p = ((ParameterizedType) t).getActualTypeArguments();
      this.aggregateClass = (Class<Entity>) p[0];
      this.modelClass = (Class<Model>) p[1];
    }
  }

  @Override
  public ID add(Entity entity) {
    //将领域对象数据复制到数据模型中
    try {
      Model model = this.modelClass.newInstance();
      ExtBeanUtils.copyProperties(entity, model);
      return (ID) this.hibernateTemplate.save(model);
    } catch (Exception e) {
      throw new UnexpectedlyException(e);
    }
  }


  @Override
  public void update(Entity entity) {
    try {
      Model model = (Model) this.hibernateTemplate.get(this.modelClass, entity.getId());
      if (model != null) {
        ExtBeanUtils.copyProperties(entity, model);
      } else {
        model = this.modelClass.newInstance();
        ExtBeanUtils.copyProperties(entity, model);
      }
      this.hibernateTemplate.update(model);
    } catch (Exception e) {
      throw new UnexpectedlyException(e);
    }
  }


  @Override
  public void removeById(ID id) {
    Model model = (Model) this.hibernateTemplate.get(this.modelClass, id);
    if (model != null) {
      this.hibernateTemplate.delete(model);
    }
  }


  @Override
  public <Entity> Entity getById(ID id) {
    Model model = (Model) this.hibernateTemplate.get(this.modelClass, id);
    if (model == null) {
      return null;
    }
    return (Entity) this.transform(model);
  }

  @Override
  public Entity getOne(Wrapper wrapper) {
    List<Entity> entityList = this.superFindListPage(wrapper, 1, 1);
    return entityList.isEmpty() ? null : entityList.get(0);
  }

  @Override
  public List<Entity> getList(Wrapper wrapper) {
    return this.superFindListPage(wrapper, 0, Integer.MAX_VALUE);
  }


  @Override
  public List<Entity> findListPage(Wrapper wrapper, Integer startRow, Integer count) {
    return this.superFindListPage(wrapper, startRow, count);
  }

  @Override
  public int getCount(Wrapper wrapper) {
    ResolveToHQL parse = new ResolveToHQL(wrapper);
    StringBuffer hql = new StringBuffer();
    hql.append("SELECT COUNT(*) FROM ");
    hql.append(modelClass.getSimpleName());
    hql.append(" ");
    hql.append(" WHERE 1=1 ");
    hql.append(parse.resolve());
    this.getLogger().debug(hql.toString());
    Query query = this.buildQuery(hql);
    return ((Long) query.uniqueResult()).intValue();
  }

  @Override
  public boolean contains(ID id) {
    return this.getById(id) == null;
  }

  @Override
  public void remove(Entity entity) {
    this.removeById(entity.getId());
  }


  /**
   * 业务模型转领域模型
   *
   * @param modelList
   * @return
   */
  protected List<Entity> transform(List<Model> modelList) {
    List<Entity> voList = modelList.stream().map((model) -> {
      return this.transform(model);
    }).collect(Collectors.toList());
    return voList;
  }

  /**
   * 业务模型转领域模型
   *
   * @return
   */
  protected Entity transform(Model model) {
    Entity entity = null;
    try {
      Constructor constructors = this.aggregateClass.getDeclaredConstructor();
      constructors.setAccessible(true);
      entity = (Entity) constructors.newInstance();
      ExtBeanUtils.copyProperties(model, entity);
    } catch (Exception e) {
      throw new UnexpectedlyException("初始化 entity 异常");
    }
    return entity;
  }

  /**
   * 建立hql query对象
   *
   * @param hql
   * @return
   */
  protected Query buildQuery(StringBuffer hql) {
    return this.buildQuery(hql.toString());
  }

  /**
   * 建立hql query对象
   *
   * @param hql
   * @return
   */
  protected Query buildQuery(String hql) {
    return this.hibernateTemplate.getSessionFactory().getCurrentSession().createQuery(hql);
  }

  /**
   * 内部方法，所有列表查询都调用本方法 改方法是Hibernate实现 注意增删改 业务必须调用该接口数据 不能用view后缀的数据
   *
   * @param wrapper
   * @param startRow
   * @param count
   * @return
   */
  protected List<Entity> superFindListPage(Wrapper wrapper, Integer startRow, Integer count) {
    ResolveToHQL parse = new ResolveToHQL(wrapper);
    StringBuffer hql = new StringBuffer();
    hql.append("FROM ");
    hql.append(modelClass.getSimpleName());
    hql.append(" ");
    hql.append(" WHERE 1=1 ");
    hql.append(parse.resolve());
    getLogger().debug(hql.toString());
    Query query = this.hibernateTemplate.getSessionFactory().getCurrentSession().createQuery(hql.toString());
    if (startRow != null && count != null) {
      query.setFirstResult((startRow - 1) * count);
      query.setMaxResults(count);
    }

    List<Model> modelList = query.list();
    if (modelList.isEmpty()) {
      return Collections.emptyList();
    }
    return this.transform(modelList);
  }

  @Override
  public <T> T getKeyView(Serializable key) {
    String keyName = this.getModelTableKey(this.modelClass);
    return this.getOneView(Wrapper.build().append(Restrictions.eq(keyName, key)));
  }

  @Override
  public <T> T getOneView(Wrapper wrapper) {
    List<T> voList = this.findListPageView(wrapper, 1, 1);
    return voList.isEmpty() ? null : voList.get(0);
  }

  @Override
  public <T> List<T> getListView(Wrapper wrapper) {
    return this.findListPageView(wrapper, null, null);
  }

  @Override
  public <T> List<T> findListPageView(Wrapper wrapper, Integer startRow, Integer count) {
    return this.superFindListPageView(wrapper, startRow, count);
  }

  /**
   * 内部方法，所有Sql执行的列表查询都调用本方法 改方法是 Mybatis 实现 注意增删改 业务不能调用该接口数据
   *
   * @param wrapper
   * @param startRow
   * @param count
   * @return
   */
  protected <T> List<T> superFindListPageView(Wrapper wrapper, Integer startRow, Integer count) {
    StringBuffer sqlString = new StringBuffer(this.getViewSql().toString());

    BaseResolve resolveToSQL = this.getViewWrapperResolve(wrapper);
    sqlString.append(" WHERE 1=1 ");
    sqlString.append(resolveToSQL.resolve());
    if (startRow != null && count != null) {
      sqlString.append(" LIMIT ").append((startRow - 1) * count).append(",").append(count);
    }
    getLogger().debug(sqlString.toString());
    List<Map<String, Object>> list = MybatisTemplate.selectList(sqlString.toString());

    List<T> categoryVoList = JSONArray.parseArray(JSONObject.toJSONString(list), this.getViewObject());

    return categoryVoList;
  }

  @Override
  public int getCountView(Wrapper wrapper) {
    StringBuffer sqlSb = new StringBuffer();
    String viewSql = this.getViewSql().toString();

    String viewSqlAry[] = viewSql.split("\\s*FROM\\s*");
    sqlSb.append("SELECT COUNT(*) AS COUNT FROM ");
    for (int i = 1; i <= viewSqlAry.length - 1; i++) {
      if (i > 1) {
        sqlSb.append(" FROM ").append(viewSqlAry[i]);
      } else {
        sqlSb.append(viewSqlAry[i]);
      }
    }
    BaseResolve resolveToSQL = this.getViewWrapperResolve(wrapper);
    sqlSb.append(" WHERE 1=1 ");
    sqlSb.append(resolveToSQL.resolve());

    Map resultM = MybatisTemplate.selectOne(sqlSb.toString());
    return Integer.parseInt(resultM.get("COUNT").toString());
  }

  /**
   * 默认抽象接口中 View 后缀的语句将调用该方法进行获取sql语句获得查询内容
   *
   * @return
   */
  protected SQL getViewSql() {
    String tableName = this.getModelTableName(this.modelClass);
    return new SQL() {{
      SELECT("*");
      FROM(tableName);
    }};
  }

  /**
   * findListPageView 中 数据模型与 方法列表数据的类型转换指定
   *
   * @return
   */
  protected Class getViewObject() {
    return aggregateClass;
  }

  /**
   * findListPageView 中 查询字段的转换
   *
   * @return
   */
  protected BaseResolve getViewWrapperResolve(Wrapper wrapper) {
    ResolveToSQL parse = new ResolveToSQL(wrapper);
    return parse;
  }

  /**
   * 获取对应数据模型的表名
   *
   * @param clzz
   * @return
   */
  protected String getModelTableName(Class clzz) {
    Annotation tableAnnotation = clzz.getAnnotation(Table.class);
    if (tableAnnotation == null) {
      throw new UnexpectedlyException("类不属于数据库模型");
    }

    return ((Table) tableAnnotation).name();
  }

  /**
   * 获取对应模型的表的主键
   *
   * @param clzz
   * @return
   */
  protected String getModelTableKey(Class clzz) {
    Annotation tableAnnotation = clzz.getAnnotation(Table.class);
    if (tableAnnotation == null) {
      throw new UnexpectedlyException("类不属于数据库模型");
    }

    Holder<Field> holder = new Holder();
    ExtReflectionUtils.doWithFields(clzz, (field) -> {
      if (field.getAnnotation(Id.class) != null) {
        holder.setValue(field);
      }
    });

    if (holder.getValue() == null) {
      throw new UnexpectedlyException("找不到对应的表主键");
    }
    return holder.getValue().getName();
  }


}

